std::function

a deep dive behind the curtain

Meeting C++ 2022

Andreas Reischuck

andreas

 

hicknhackLogo new text

 

Not an Expert

Disclaimer

Let’s go

deepai letsgo

Simple C++ function

void basic_function() {}
auto trailing_basic_function() -> void {}

Function signature are types

using VoidFunction = void();
using TrailingVoidFunction = auto() -> void;

Variable with pointer to function

using VoidFunction = void();
using VoidFunctionPtr = VoidFunction*;
auto basic_function_ptr =
  VoidFunctionPtr{&basic_function};
basic_function_ptr(); // calls basic_function

constexpr function pointer

constexpr auto constexpr_basic_function_ptr =
  VoidFunctionPtr{&basic_function};
constexpr_basic_function_ptr();

Summary

Why use std::function?

Callback Example

deepai callback

Button triggers callback when clicked.

Use Function Pointers

struct Button {
  VoidFunctionPtr clicked;
};
struct EditDialog {
  Button okButton;
  Button cancelButton;
  void onOkClicked();
  void onCancelClicked();
};

Challenge:

How can we call the instance methods of Dialog on clicked?

using VoidVoidPtrFunction = void(void*);
struct Button {
  void* clicked_instance;
  VoidVoidPtrFunction clicked;
};

Function Pointers

Object Oriented Approach

struct ClickableInterface {
  virtual ~ClickableInterface() = default;
virtual void onClicked() = 0; };
struct EditDialog {
  Button okButton;
  Button cancelButton;
  // Puh…
};

Object Oriented Approach

Clickable Interface

The Elephant in the Room bigger

Use std::function

#include <functional>
using CallbackFunc = std::function<void()>;
struct Button {
  CallbackFunc clicked;
};
EditDialog::EditDialog() {
  okButton.clicked = [this]() {
    this->onOkClicked();
  };
}

Use std::function

Extreme Example

deepai happy cpp hackking

Task Scheduler

struct Scheduler {
  using Task = std::function<void()>;
void queueUpTask(const Task&);
private:
  std::queue<Task> queue;
};

Summary

std::function

deepai curtain

Naive implemenation of std::function

Function template signature

using IntFunction = Function<void(int)>;
template</*What goes here?*/>
struct Function {};
// Partial Template Specialisation
template<class Ret, class... Args>
struct Function<Ret(Args...)> {};

Call Interface

template<class Ret, class... Args>
struct Function<Ret(Args...)> {
  /*?*/ operator() (/*args? */) const {

  }
  struct CallInterface {
    virtual Ret call(Args...) = 0;
  };
  std::shared_ptr<CallInterface> m_ptr;
};

Call Implementation

template<class Ret, class... Args>
struct Function<Ret(Args...)> {                   //
  template<class Callable>
  struct CallImpl;
};

Constructor

template<class Ret, class... Args>
struct Function<Ret(Args...)> {
  template<class Callable>
  requires(!std::is_same_v<Callable, Function>)
Function(const Callable& callable)
    : m_ptr{new CallImpl<Callable>{callable}} {}
  /*snip*/
  CallImpl(const Callable& callable)
    : m_callable{callable} {}
};

Naive Function Code

#include <memory>

template<class Signature> struct Function;

template<class Ret, class... Args>
struct Function<Ret(Args...)> {
  Function() = default;
  template<class Callable>
  requires(!std::is_same_v<Callable, Function>)
  Function(const Callable& callable) : m_ptr{new CallImpl<Callable>{callable}} {}

  Ret operator() (Args... args) const { return m_ptr->call((Args)args...); }

private:
  struct CallInterface {
    virtual Ret call(Args...) = 0;
  };
  template<class Callable>
  struct CallImpl final : CallInterface {
    Callable m_callable;
    CallImpl(const Callable& callable) : m_callable{callable} {}

    Ret call(Args... args) override { return std::invoke(m_callable, (Args)args...); }
  };
  std::shared_ptr<CallInterface> m_ptr;
};

Summary

std::function interface

We have some already…

Empty State

If a std::function contains no target, it is called empty.

Invoking the target of an empty std::function results in std::bad_function_call exception being thrown.

cppreference.com

Operator bool

explicit operator bool() const noexcept {
  return m_ptr;
}

Nullptr Constructor

Function(std::nullptr_t) noexcept {}
Function& operator=(std::nullptr_t) noexcept {
  m_ptr.reset();
  return *this;
}

Nullptr Comparison

// note: C++20 generates other variants!
template<class Sig> bool operator==(const Function<Sig>& f, std::nullptr_t) noexcept {
  return !f;
}

Swap

void swap(Function& other) noexcept {
  std::swap(m_ptr, other.m_ptr);
}
template<class Sig>
void swap(Function<Sig> &lhs,
          Function<Sig> &rhs) noexcept {
  lhs.swap(rhs);
}

Member Types

using result_type = Ret;
// deprecated in C++17, removed in C++20:
// using argument_type,
// using first_argument_type
// using second_argument_type;

Member pointers

… as well as pointers to member functions and pointers to data members.

cppreference.com

Data member pointer usage

#include <functional>

struct Example {
  int memberData = 2;
};
using F = std::function<int(Example*)>;
int main() {
  auto example = Example{};
  auto dataFunc = F{&Example::memberData};
  std::cout << dataFunc(&example) << '\n';
}

Member function pointer usage

#include <functional>

struct Example {
  int memberFunction() { return 3; }
}; using F = std::function<int(Example*)>; int main() { auto example = Example{};
  auto memberFunc = F{&Example::memberFunction};
  std::cout << memberFunc(&example) << '\n';
}

Target type

const std::type_info& target_type() const noexcept;
template<class T> T* target() noexcept;
template<class T> const T* target() const noexcept;

Summary std::function interface

Recommended practice: Implementations should avoid the use of dynamically allocated memory for small callable objects, for example, where f’s target is an object holding only a pointer or reference to an object and a member function pointer.

C++ Standard

Comparison

Other Implementations

Sonic amilibo

Runtime

HummingBird BY SA 2.0

Small Object Optimization

Contestant x86_32 x86_64

MS-STL

8+2 ptr

6+2 ptr

libstdc++

libc++

fb-folly

function2

Small Object Optimization

Contestant x86_32 x86_64

MS-STL

8+2 ptr

6+2 ptr

libstdc++

2+2 ptr

2+2 ptr

libc++

fb-folly

function2

Small Object Optimization

Contestant x86_32 x86_64

MS-STL

8+2 ptr

6+2 ptr

libstdc++

2+2 ptr

2+2 ptr

libc++

2+? ptr

2+4 ptr

fb-folly

function2

Small Object Optimization

Contestant x86_32 x86_64

MS-STL

8+2 ptr

6+2 ptr

libstdc++

2+2 ptr

2+2 ptr

libc++

2+? ptr

2+4 ptr

fb-folly

6+2 ptr

6+2 ptr

function2

Small Object Optimization

Contestant x86_32 x86_64

MS-STL

8+2 ptr

6+2 ptr

libstdc++

2+2 ptr

2+2 ptr

libc++

2+? ptr

2+4 ptr

fb-folly

6+2 ptr

6+2 ptr

function2

6+2 ptr

2+2 ptr

Possible Extensions

deepai extensions

Customize call qualifiers

using F2 = fu2::function<void(int) noexcept>;
// template instance pseudo code:
void function::operator() (int) const noexcept;
template<class Callable>
requires(noexcept(Callable))
function(Callable&);

Multiple Overloads

using F2 = fu::function<void(int), void(float)>;
struct Example {
  void operator() (int x) {
    std::print("int: {}\n", x); }
  void operator() (double x) {
    std::print("double: {}\n", x); }
};
auto f2 = F2{Example{}};
f2(2);
f2(3.14);

More Ideas

std::move_only_function

Summary

andreas

 

hicknhackLogo new text

 

cppug

 

Give a Talk
⇒ get a Dresden tour

rebuild logo

Rebuild language project

 

Collaborate

Hack more & learn everything!

Photo Credits

std::function

a deep dive behind the curtain

co_await question_ready()

Bonus Quiz

deepai puzzle

What is the size? (1)

int demo(double x) {
  return static_cast<int>(x);
}

int main() {
  std::cout << sizeof(demo);
}

What is the size? (2)

int demo(double x) {
  return static_cast<int>(x);
}

int main() {
  auto ptr = &demo;
  std::cout << sizeof(ptr);
}

What is the size? (3)

struct Demo {
  int memberFunc(double x) {
    return static_cast<int>(x);
  }
};

int main() {
  auto ptr = &Demo::memberFunc;
  std::cout << sizeof(ptr);
}

Reduce Size for members

struct Demo {
  int memberFunc(double x) {
    return static_cast<int>(x);
  }
};
using DemoFunc = int(Demo*, double);
constexpr DemoFunc* ptr = [](Demo* demo, double x) {
    return demo->memberFunc(x);
  };
int main() {
  std::cout << sizeof(ptr);
}

std::exit(0)